<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body{
            margin: 0;
            overflow: hidden;
        }
    </style>
    <script src="../src/three.js"></script>
    <script src="../src/OrbitControls.js"></script>
</head>
<body>
    
</body>
<script>
    var fireVertexShader = `
        attribute vec4 orientation;
        attribute vec3 offset;
        attribute vec2 scale;
        attribute float life;
        attribute float random;

        varying vec2 vUv;
        varying float vRandom;
        varying float vAlpha;

        float range(float oldValue, float oldMin, float oldMax, float newMin, float newMax) {
            float oldRange = oldMax - oldMin;
            float newRange = newMax - newMin;
            return (((oldValue - oldMin) * newRange) / oldRange) + newMin;
        }

        // From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm
        float pcurve(float x, float a, float b) {
            float k = pow(a + b, a + b) / (pow(a, a) * pow(b, b));
            return k * pow(x, a) * pow(1.0 - x, b);
        }

        void main() {
            vUv = uv;
            vRandom = random;

            vAlpha = pcurve(life, 1.0, 2.0);

            vec3 pos = position;

            pos.xy *= scale * vec2(range(pow(life, 1.5), 0.0, 1.0, 1.0, 0.6), range(pow(life, 1.5), 0.0, 1.0, 0.6, 1.2));

            vec4 or = orientation;
            vec3 vcV = cross(or.xyz, pos);
            pos = vcV * (2.0 * or.w) + (cross(or.xyz, vcV) * 2.0 + pos);

            gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
        }`;
    
    var fireFragmentShader = `
        uniform sampler2D uMap;
        uniform vec3 uColor1;
        uniform vec3 uColor2;
        uniform float uTime;

        varying vec2 vUv;
        varying float vAlpha;
        varying float vRandom;

        void main() {
            vec2 uv = vUv;

            float spriteLength = 10.0;
            uv.x /= spriteLength;
            float spriteIndex = mod(uTime * 0.1 + vRandom * 2.0, 1.0);
            uv.x += floor(spriteIndex * spriteLength) / spriteLength;

            vec4 map = texture2D(uMap, uv);

            gl_FragColor.rgb = mix(uColor2, uColor1, map.r);
            gl_FragColor.a = vAlpha * map.a;
        }
        `;
    
    var embersVertexShader = `
        attribute float size;
        attribute float life;
        attribute vec3 offset;

        varying float vAlpha;

        // From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm
        float impulse(float k, float x) {
            float h = k * x;
            return h * exp(1.0 - h);
        }

        void main() {
            vAlpha = impulse(6.28, life);

            vec3 pos = position;
            pos += offset * vec3(life * 0.7 + 0.3, life * 0.9 + 0.1, life * 0.7 + 0.3);

            vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
            gl_PointSize = size * (80.0 / length(mvPosition.xyz));
            gl_Position = projectionMatrix * mvPosition;
        }
        `;

        var embersFragmentShader = `
        uniform sampler2D uMap;
        uniform vec3 uColor;

        varying float vAlpha;

        void main() {
            vec2 uv = vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y);
            vec4 mask = texture2D(uMap, uv);

            gl_FragColor.rgb = uColor;
            gl_FragColor.a = mask.a * vAlpha * 0.8;
        }
        `;

    var hazeVertexShader = `
        attribute vec3 base;
        attribute vec3 offset;
        attribute vec4 orientation;
        attribute vec2 scale;
        attribute float life;

        varying float vAlpha;
        varying vec2 vUv;

        // From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm
        float impulse(float k, float x) {
            float h = k * x;
            return h * exp(1.0 - h);
        }

        float pcurve(float x, float a, float b) {
            float k = pow(a + b, a + b) / (pow(a, a) * pow(b, b));
            return k * pow(x, a) * pow(1.0 - x, b);
        }

        void main() {
            vUv = uv;
            vAlpha = pcurve(life, 1.0, 2.0);

            vec3 pos = position;

            pos.xy *= scale * (life * 0.7 + 0.3);

            vec4 or = orientation;
            vec3 vcV = cross(or.xyz, pos);
            pos = vcV * (2.0 * or.w) + (cross(or.xyz, vcV) * 2.0 + pos);

            pos += base;
            pos += offset * vec3(life * 0.7 + 0.3, life * 0.9 + 0.1, life * 0.7 + 0.3);

            gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);;
        }
        `;

    var hazeFragmentShader = `
        uniform sampler2D uMap;
        uniform sampler2D uMask;
        uniform vec2 uResolution;

        varying float vAlpha;
        varying vec2 vUv;

        void main() {
            vec2 uv = gl_FragCoord.xy / uResolution;
            vec2 mask = texture2D(uMask, vUv).ra - vec2(0.5);
            uv -= mask * 0.1;
            vec4 tex = texture2D(uMap, uv);

            gl_FragColor.rgb = tex.rgb;
            gl_FragColor.a = vAlpha * 0.5;
        }
        `;
</script>
<script>
    var scene, camera, renderer, clock, controlller
    var fire, _rtt
    init()
    animate()

    // - Functions -
    function init(){
        scene = new THREE.Scene()
        clock = new THREE.Clock();
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
        camera.position.z = 10
        renderer = new THREE.WebGLRenderer({
            antialias:true, // 开启抗锯齿处理
            alpha:true
        })
        renderer.setPixelRatio( window.devicePixelRatio )
        renderer.setSize(window.innerWidth,window.innerHeight)
        renderer.setClearColor(new THREE.Color(0x00000),0.5)

        var axisHelper = new THREE.AxesHelper( 10 )
        scene.add( axisHelper )

        init_fire()     // 火焰的主体
        initEmbers()    // 飘飞的火星
        // resetRT()
        // initHaze()

        controlller = new THREE.OrbitControls(camera,renderer.domElement)
        document.body.appendChild(renderer.domElement)
        window.onresize = onResize
    }

    
    function resetRT() {
        var _parameters = {
            minFilter: THREE.LinearFilter,
            magFilter: THREE.LinearFilter,
            format: THREE.RGBAFormat,
            stencilBuffer: false
        };
        if (_rtt) _rtt.dispose();
        _rtt = new THREE.WebGLRenderTarget(window.innerWidth * 0.5, window.innerHeight * 0.5, _parameters);
    }

    function init_fire(){
        var _geometry, _shader, mesh, group;
        // group 火焰组对象
        // 火焰其实是由许多火焰状的平面绕 Y 轴旋转形成的 从 Y 轴观看可以得知
        // _num 表示平面的数量
        var _num = 64 

        var _x = new THREE.Vector3(1, 0, 0);
        var _y = new THREE.Vector3(0, 1, 0);
        var _z = new THREE.Vector3(0, 0, 1);

        var _tipTarget = new THREE.Vector3();
        var _tip = new THREE.Vector3();
        var _diff = new THREE.Vector3();

        var _quat = new THREE.Quaternion();
        var _quat2 = new THREE.Quaternion();

        (function() {
            initGeometry();
            initInstances();
            initShader();
            initMesh();
            requestAnimationFrame(loop);
        })();

        function initGeometry() {
            _geometry = new THREE.InstancedBufferGeometry();
            _geometry.maxInstancedCount = _num;

            var shape = new THREE.PlaneBufferGeometry(1, 1);
            shape.translate(0, 0.4, 0);
            var data = shape.attributes;

            _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(data.position.array), 3));
            _geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(data.uv.array), 2));
            _geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(data.normal.array), 3));
            _geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(shape.index.array), 1));
            shape.dispose();
        }

        function initInstances() {
            var orientation = new THREE.InstancedBufferAttribute(new Float32Array(_num * 4), 4);
            var randoms = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1);
            var scale = new THREE.InstancedBufferAttribute(new Float32Array(_num * 2), 2);
            var life = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1);

            for (let i = 0; i < _num; i++) {
                orientation.setXYZW(i, 0, 0, 0, 1);
                life.setX(i, i / _num + 1);
            }

            _geometry.addAttribute('orientation', orientation);
            _geometry.addAttribute('scale', scale);
            _geometry.addAttribute('life', life);
            _geometry.addAttribute('random', randoms);
        }

        function initShader() {
            var uniforms = {
                uMap: {
                    type: 't',
                    value: null
                },
                uColor1: {
                    type: 'c',
                    value: new THREE.Color(0x961800)
                }, // red
                uColor2: {
                    type: 'c',
                    value: new THREE.Color(0x4b5828)
                }, // yellow
                uTime: {
                    type: 'f',
                    value: 0
                },
            };

            _shader = new THREE.ShaderMaterial({
                uniforms: uniforms,
                vertexShader: fireVertexShader,
                fragmentShader: fireFragmentShader,
                blending: THREE.AdditiveBlending,
                transparent: true,
                depthWrite: false,
                side: THREE.DoubleSide,
            });

            var textureLoader = new THREE.TextureLoader();
            textureLoader.load('flame.png', t => _shader.uniforms.uMap.value = t);
        }

        function initMesh() {
            group = new THREE.Group()
            mesh = new THREE.Mesh(_geometry, _shader)
            mesh.frustumCulled = false
            group.add(mesh)
            scene.add(group)
            fire = group
        }

        function loop(e) {
            requestAnimationFrame(loop);
            _shader.uniforms.uTime.value = e * 0.001;

            var life = _geometry.attributes.life;
            var orientation = _geometry.attributes.orientation;
            var scale = _geometry.attributes.scale;
            var randoms = _geometry.attributes.random;

            for (let i = 0; i < _num; i++) {
                var value = life.array[i];
                value += 0.04;

                if (value > 1) {
                    value -= 1;

                    _quat.setFromAxisAngle(_y, random(0, 3.14, 3));
                    _quat2.setFromAxisAngle(_x, random(-1, 1, 2) * 0.1);
                    _quat.multiply(_quat2);
                    _quat2.setFromAxisAngle(_z, random(-1, 1, 2) * 0.3);
                    _quat.multiply(_quat2);
                    orientation.setXYZW(i, _quat.x, _quat.y, _quat.z, _quat.w);

                    scale.setXY(i, random(0.8, 1.2, 3), random(0.8, 1.2, 3));
                    randoms.setX(i, random(0, 1, 3));
                }

                life.setX(i, value);
            }
            life.needsUpdate = true;
            orientation.needsUpdate = true;
            scale.needsUpdate = true;
            randoms.needsUpdate = true;

            // group.position.x = Math.sin(e * 0.002) * 1.4;
            // group.position.y = Math.cos(e * 0.0014) * 0.2;
            // group.position.z = Math.cos(e * 0.0014) * 0.5;

            let tipOffset = 0.4;
            _tipTarget.copy(group.position);
            _tipTarget.y += tipOffset;
            _tip.lerp(_tipTarget, 0.1);

            _diff.copy(_tip);
            _diff.sub(group.position);
            let length = _diff.length();
            group.scale.y = (length / tipOffset - 1) * 0.4 + 1;

            group.quaternion.setFromUnitVectors(_y, _diff.normalize());
        }
    }

    function initEmbers() {
        var _geometry, _shader, _points;
        var _num = 16;   // 火星数量

        (function() {
            initGeometry();
            initShader();
            initMesh();
            requestAnimationFrame(loop);
        })();

        function initGeometry() {
            _geometry = new THREE.BufferGeometry();
            _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(_num * 3), 3));
            _geometry.addAttribute('offset', new THREE.BufferAttribute(new Float32Array(_num * 3), 3));
            _geometry.addAttribute('size', new THREE.BufferAttribute(new Float32Array(_num), 1));
            _geometry.addAttribute('life', new THREE.BufferAttribute(new Float32Array(_num), 1));

            for (var i = 0; i < _num; i++) {
                _geometry.attributes.life.setX(i, random(0, 1, 3) + 1);
            }
        }

        function initShader() {
            var uniforms = {
                uMap: {
                    type: 't',
                    value: null
                },
                uColor: {
                    type: 'c',
                    value: new THREE.Color(0xffe61e)
                },
            };

            _shader = new THREE.ShaderMaterial({
                uniforms: uniforms,
                vertexShader: embersVertexShader,
                fragmentShader: embersFragmentShader,
                blending: THREE.AdditiveBlending,
                transparent: true,
                depthTest: false,
            });

            var textureLoader = new THREE.TextureLoader();
            textureLoader.load('ember.png', t => _shader.uniforms.uMap.value = t);
        }

        function initMesh() {
            _points = new THREE.Points(_geometry, _shader);
            _points.frustumCulled = false;
            scene.add(_points);
        }

        function loop() {
            requestAnimationFrame(loop);
            var life = _geometry.attributes.life;
            var position = _geometry.attributes.position;
            var size = _geometry.attributes.size;
            var offset = _geometry.attributes.offset;
            for (let i = 0; i < _num; i++) {
                var value = life.array[i];
                value += 0.02;

                if (value > 1) {
                    value -= 1;

                    position.setXYZ(i, fire.position.x, fire.position.y + 0.1, fire.position.z);
                    offset.setXYZ(i,
                        random(-0.2, 0.2, 3),
                        random(0.7, 1.2, 3),
                        random(-0.2, 0.2, 3)
                    );
                    size.setX(i, random(0.2, 0.5, 3));
                }

                life.setX(i, value);
            }

            life.needsUpdate = true;
            position.needsUpdate = true;
            size.needsUpdate = true;
            offset.needsUpdate = true;
        }
    }

    function animate(){
        fire.rotation.copy(camera.rotation)
        requestAnimationFrame(animate)
        renderer.render(scene, camera)
        controlller.update(clock.getDelta())
    }

    function random(min, max, precision) {
        var p = Math.pow(10, precision);
        return Math.round((min + Math.random() * (max - min)) * p) / p;
    }

    function onResize() {
        camera.aspect = window.innerWidth / window.innerHeight
        camera.updateProjectionMatrix()
        renderer.setSize(window.innerWidth, window.innerHeight)
    }

    
</script>
</html>